<?php
/**
 * Created by PhpStorm.
 * User: xuzengqiang
 * Date: 2016-11-11
 * Time: 9:06
 */

namespace Once\Docgen;

/**
 * Class SwaggerJson
 * @package Once\Docgen
 * TODO * SwaggerJson不支持的所有注释, 记入externalDocs, 以x-??形式命名
 */
class SwaggerJson {

    public function __construct($dirInfo,
                                $host,
                                $basePath = '/',
                                $title = '',
                                $description = '',
                                $version = '',
                                $schemes = ['http'],
                                $produces = ['application/json'],
                                $consumes = ['application/json']) {
        $this->dirInfo = $dirInfo;
        $this->swaggerDict = array(
            'swagger' => '2.0',
            'info' => array(
                'title' => $title,
                'description' => $description,
                'version' => $version
            ),
            'host' => $host,
            'schemes' => $schemes,
            'basePath' => $basePath,
            'produces' => $produces,
            'consumes' => $consumes,
            'paths' => [],
            'tags' => []
        );
    }


    private function parseSingleParam($param) {

        if ($param->type) {
            $data = $this->parseNodes($param, true, 'param');

            if ($data) {
                if (strpos($param->source, 'request.query') !== false) {
                    $in = 'query';
                } else if (strpos($param->source, 'request.route') !== false) {
                    $in = 'path';
                } else {
                    $in = 'formData';
                }
                $data['in'] = $in;
                $data['description'] = $param->doc;
                $data['name'] = $param->name;
                $data['required'] = $param->isOptional ? false : true;
            }
            return $data;
        }

    }


    private function parseNormalResponse($response) {
        if (isset($response['$.response.content'])) {
            $responseDict = $this->parseNodes($response['$.response.content'], true);
        } else {
            $responseDict = array(
                'description' => '',
                'schema' => array(
                    'type' => 'object',
                    'properties' => array()
                )
            );
            foreach ($response as $k => $v) {
                $basicData = $this->parseNodes($v);
                $tempArr = explode('.', $k);
                $responseDict['schema']['properties'][$tempArr[count($tempArr) - 1]] = $basicData;
            }
        }

        return array("200" => $responseDict);
    }

    private function parseExceptionResponse($exceptions) {
        $ret = array();
        foreach ($exceptions as $v) {

            $methods = get_class_methods($v[0]);
            if (is_array($methods) && in_array('getStatusCode', $methods)) {
                $exception = new $v[0];
                $ret[$exception->getStatusCode()] = array(
                    'description' => $v[1]
                );
            } else {
                $ret['500'] = array(
                    'description' => $v[1]
                );
            }
        }
        return $ret;
    }

    private function parseEntity($entity) {
        $property = [];
        $entity = \Once\Api::getInstance()->getEntity($entity);
        $required = [];
        foreach ($entity->getProperties() as $k => $v) {
            if (!$v->isOptional) {
                $required[] = $k;
            }
            $property[$k] = $this->parseNodes($v);
        }
        return array($property, $required);

    }


    private function parseNodes($entity, $top=false, $type='response') {
        if (in_array($entity->type, ['string', 'int', 'float', 'boolean', null])) {
            $data = array('description' => $entity->doc);
            if (!$top || $type == 'param') {
                $data['type'] = $entity->type == 'int' ? 'integer' : 'string';
            }
        } else if ($entity->type == 'array') {
            $data = array(
                'type' => 'array',
                'description' => '',
                'items' => array(
                    'type' => 'string'
                )
            );
            if ($top) {
                $data = array(
                    'description' => '',
                    'schema' => $data

                );
            }

        } else if (substr($entity->type, -2, 2) == '[]') {
            $parseEntityData = $this->parseEntity(substr($entity->type, 0, -2));
            $data = array(
                'type' => 'array',
                'items' => array(
                    'type' => 'object',
                    'properties' => $parseEntityData[0]
                )
            );
            if (!empty($parseEntityData[1])) {
                $data['items']['required'] = $parseEntityData[1];
            }
            if ($top) {
                $data = array(
                    'description' => '',
                    'schema' => $data

                );
            }

        } else {
            $parseEntityData = $this->parseEntity($entity->type);
            $data = array(
                'type' => 'object',
                'properties' => $parseEntityData[0]
            );
            if (!empty($parseEntityData[1])) {
                $data['required'] = $parseEntityData[1];
            }
            if ($top) {
                $data = array(
                    'description' => '',
                    'schema' => $data

                );
            }
        }
        return $data;
    }


    public function generateSwaggerData() {
        foreach ($this->dirInfo as $classInfo) {
            $pathPrefix = $classInfo->getPathPrefix();
            $routes = $classInfo->getRoutes();
            $module_doc_arr = explode("\n", $classInfo->getDoc());

            $this->swaggerDict['tags'][] = array(
                'name' => $classInfo->getClassName(),
                'description' => count($module_doc_arr) >= 1 ? trim($module_doc_arr[0]) : ''
            );
            foreach ($routes as $func => $route) {
                $uri = $pathPrefix . $route->getUri();
                $method = strtolower($route->getMethod());
                $doc_arr = explode("\n", $route->getDoc());
                $summary = count($doc_arr) > 0 ? trim($doc_arr[0]) : '';
                $description = count($doc_arr) > 1 ? trim($doc_arr[1]) : $summary;
                $params = $route->getActionInvoker()->getParamsBuilder()->getParams();
                $paramsList = [];
                foreach ($params as $v) {
                    $singleParamData = $this->parseSingleParam($v);
                    if ($singleParamData) {
                        $paramsList[] = $singleParamData;
                    }
                }
                $response = $route->getActionInvoker()->getReturnHandler()->getMappings();
                $exceptions = $route->getActionInvoker()->getExceptions();
                if (!isset($this->swaggerDict['paths'][$uri])) {
                    $this->swaggerDict['paths'][$uri] = [];
                }

                $annotation = $classInfo->getMethodAnnotations($func);
                $methodDoc = array();
                $notIns = array('param', 'path', 'return', 'route', 'throws');
                foreach ($annotation as $v){
                    if(in_array($v[0], $notIns)){continue;}
                    $methodDoc['x-'.$v[0]] = $v[1][0];
                }

                $this->swaggerDict['paths'][$uri][$method] = array(
                    'tags' => [$classInfo->getClassName()],
                    'summary' => $summary,
                    'description' => $description,
                    'operationId' => $func,
                    'responses' => $this->parseNormalResponse($response) + $this->parseExceptionResponse($exceptions),
                    'externalDocs'=> $methodDoc
                );
                if ($paramsList) {
                    $this->swaggerDict['paths'][$uri][$method]['parameters'] = $paramsList;
                }
            }
        }
//        echo json_encode($this->swaggerDict);
        return $this->swaggerDict;
    }

    private $swaggerDict;
    private $dirInfo;

}